import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { TreeComponentInterface } from '../tree.interface';
// import { TreeComponent } from '../tree.component';

/**
 * Controller for tree selection
 * Note: for Observable like dataSource, only works for BehaviorSubject
 */
export class TreeSelectControl<T> {

  constructor(
    private _tree: TreeComponentInterface<T>,
  ) {}

  refreshSelectState(data: T) {
    const selected = this._getSelected(data);
    this._setSelected(data, selected);
    this.conductUp(data);
    this.conductDown(data, selected);
  }

  conductUp(data: T) {
    const parentData = this._getParentData(data, this._getDataSource());
    if (parentData) {
      if (this._getChildren(parentData).every((_data) => !this._getHalfSelected(_data) && this._getSelected(_data))) {
        this._setSelected(parentData, true);
      } else if (this._getChildren(parentData).some((_data) => this._getHalfSelected(_data) || this._getSelected(_data))) {
        this._setSelected(parentData, false, true);
      } else {
        this._setSelected(parentData, false);
      }
      this.conductUp(parentData);
    }
  }

  conductDown(data: T, selected: boolean) {
    const descendants = this._tree.treeControl.getDescendants(data);
    descendants.forEach((_data) => {
      this._setSelected(_data, selected);
    });
  }

  getSelectedData() {
    const result: T[] = [];

    const _doGetSelectedData = (_result: T[], roots: T[]) => {
      for (const root of roots) {
        if ((root as any).selected) {
          _result.push(root);
        }
        const children = this._getChildren(root);
        if (children) {
          _doGetSelectedData(_result, children);
        }
      }
    };
    _doGetSelectedData(result, this._getDataSource());

    return result;
  }

  private _getDataSource(): T[] {
    const result: T[] = [];

    const _doGetDataSource = (_result) => {
      if (Array.isArray(this._tree.dataSource)) {
        _result.push(...this._tree.dataSource);
      } else if (this._tree.dataSource instanceof Observable) {
        this._tree.dataSource.pipe(take(1)).subscribe((data) => {
          _result.push(...data);
        });
      }
    };
    _doGetDataSource(result);

    return result;
  }

  private _getChildren(data: T): T[] {
    const result: T[] = [];

    const _doGetChildren = (_result, _data) => {
      const childrenNodes = this._tree.treeControl.getChildren(_data);
      if (Array.isArray(childrenNodes)) {
        _result.push(...childrenNodes);
      } else if (childrenNodes instanceof Observable) {
        childrenNodes.pipe(take(1)).subscribe((children) => {
          _result.push(...children);
        });
      }
    };

    _doGetChildren(result, data);

    return result;
  }

  private _getSelected(data: T): boolean {
    return (data as any).selected;
  }

  private _getHalfSelected(data: T): boolean {
    return (data as any).halfSelected;
  }

  private _setSelected(data: T, selected: boolean, halfSelected: boolean = false) {
    (data as any).selected = selected;
    (data as any).halfSelected = halfSelected;
  }

  private _getParentData(data: T, roots: T[]): T {
    let result: T;
    for (const root of roots) {
      const children = this._getChildren(root);
      if (children) {
        if (children.find(child => child === data)) {
          result = root;
          return result;
        } else {
          result = result || this._getParentData(data, children);
        }
      }
    }
    return result;
  }
}
